package mygame;

import mygame.hudelements.HudControl;
import mygame.hudelements.AltimeterControl;
import mygame.hudelements.AltOverGroundControl;
import mygame.hudelements.HorizonControl;
import com.jme3.app.SimpleApplication;
import com.jme3.input.controls.ActionListener;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;

/**
 * Main class for the flight simulator
 * @author aicher
 * @version 0.0.3
 */
public class Main extends SimpleApplication implements ActionListener{
    /**
     * Use this factor to convert the JMonkey coordinates in feet
     */
    public static final float COORD_IN_FEET_FACTOR = 3f;    
    /**
     * Central gravity value
     */
    public static final float GRAVITY_VALUE = 9.71f;
    /**
     * Material definition of the terrain
     */
    Material mat_terrain = null;
    
    /**
     * Terrain quads - the terrain that will be displayed
     */
    TerrainQuad terrain = null;
    
    /**
     * Reference to the user steered plane
     */
    private MyPlane myPlane = null;
    
    /**
     * This object contains the complete camera steering functionality
     */
    private MyCamWrapper myCam = null;
    
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        KeyMapping.doMappings(inputManager, this);
        this.initLightAndShadow();
        this.initTerrain();
        this.initMyPlane();
        this.initCam();
        this.initHud();
    }

    /**
     * Cares for the terrain settings. The terrain is based on the predefined "Terrain.j3md"
     * material definition from JMonkeyEngine and supports "only" three textures based on 
     * the RGB alphamap. 
     */
    private void initTerrain() {
        // as long as we do not have a "sky", we just use a blue background
        viewPort.setBackgroundColor(ColorRGBA.Blue);
        
        // now load the material definition for our terrain
        // We use the predefined "Terrain.j3md" which supports three
        // diffuse maps (and three normal maps which are not used in our example)
        // The definition, where the three textures of the normal maps are
        // placed is held in the alpha map:
        // Red: Tex1
        // Green: Tex2
        // Blue: Tex3
        mat_terrain = new Material(assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");
        
        // now load the alpha map for the mapping of the three diffuse maps
        mat_terrain.setTexture("Alpha", assetManager.loadTexture(
                "Textures/terrain.png"));
        
        // Reached this stage, the three images for the diffuse maps can be
        // loaded. We begin with the rock texture
        Texture rock = assetManager.loadTexture(
                "Textures/Rock.png");
        // now set the image as repeat - be shure that the rock.jpg is 
        // tileable
        rock.setWrap(Texture.WrapMode.Repeat);
        // Now link the rock texture to the red color of the alpha map
        mat_terrain.setTexture("Tex1", rock);
        // The scale value defines, how the image (here rock.jpg) is scaled and
        // mapped to the surface. The smaller the value is, the more the image
        // is "streched" on the surface
        mat_terrain.setFloat("Tex1Scale", 256f);
        
        // now we can load the other two textures:
        Texture grass = assetManager.loadTexture(
                "Textures/grass.jpg");
        grass.setWrap(Texture.WrapMode.Repeat);
        mat_terrain.setTexture("Tex2", grass);
        mat_terrain.setFloat("Tex2Scale", 512f);
        
        Texture street = assetManager.loadTexture(
                "Textures/road.png");
        street.setWrap(Texture.WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", street);
        mat_terrain.setFloat("Tex3Scale", 128);

        // now we set the height information, located in a greyscale highmap
        Texture heightMapImage = assetManager.loadTexture(
                "Textures/highmap.png");
        AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();

        // Now the terrain can be created It thakes the tile size, where the 
        // texture images are inserted (size must be power of 2 + 1) and
        // the real size of the terrain (size must be power of 2 + 1). You also
        // must provide a name (for proper collision detection) and the heightmap
        terrain = new TerrainQuad("my terrain", 65, 1025, heightmap.getHeightMap());
        
        // The previously material can be set
        terrain.setMaterial(mat_terrain);
        // the position of the terrain is usually 0,0,0
        terrain.setLocalTranslation(0, 0, 0);
        
        // Now you can scale the terrain in the x, y and z direction. 
        // The Terrain size is now 1025 * 16 x 1025 * 16 (x and z). Therefore
        // the coordinates of the plane can vary from +-8200 (x and z)
        // The height is scaled with factor 3
        terrain.setLocalScale(16f, 3f, 16f);
        
        // the terrain must be of course attached to the root node
        rootNode.attachChild(terrain);

        // set the TerrainLodControl in order to allow JMonkey to reduce the 
        // level of detail of far triangles
        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
        terrain.addControl(control);       
        
        // force JME to increase the frustum in order to allow more far views
        cam.setFrustumFar(8000f);
        cam.onFrameChange();
    }
    
    /**
     * Initializes the camera wrapper class
     */
    private void initCam() {
        this.myCam = new MyCamWrapper(cam);
        
        // Until we do not have a toggle mechanism, we control on code level if the
        // camera is mounted on the plane, or is positioned absoluteley
        // this.myCam.setPosition(new Vector3f(0f, 100f, 0f), new float[] {0f, 0f, 0f});
        
        // Here we mount the camera on the plane
        this.myCam.setPlane(myPlane);
    }
    /**
     * Creates my Plane and places it into the scene graph
     */
    private void initMyPlane() {
        this.myPlane = new MyPlane(assetManager,
                rootNode, "Models/SimplePlane/SimplePlane.j3o",
                new Vector3f(0f, 150f, 0f), PlaneBehaviour.SMALL_PLANE);
        
        // Control for moving the plane
        this.myPlane.planeNode.addControl(new PlaneBasicControl(this.myPlane.planeBehaviour, this.terrain));
        this.myPlane.planeNode.getControl(PlaneBasicControl.class).setEnabled(true);
    }
    
    /**
     * Initializes the head up display and registeres all necessary instruments
     */
    private void initHud() {
        // All measures of the instruments will based on the reference size  - which will be a grid
        // whith this reference size
        float hudReferenceSize = settings.getWidth() / 200.0f;
        
        // Control for displaying text on the screen
        HudControl myHudControl = new HudControl(guiFont, guiNode, assetManager, settings);
        this.myPlane.planeNode.addControl(myHudControl);
        
        // Register altimeter and place in the HUD
        myHudControl.registerNewInstrument(new AltimeterControl(
                guiNode, assetManager,
                160,
                0,
                hudReferenceSize,
                40,
                40,
                this.settings.getWidth(),
                true,
                0.1f));
        
        // Register artificial horizon instrument and place in the HUD
        myHudControl.registerNewInstrument(new HorizonControl(
                guiNode, assetManager,
                120,
                0,
                hudReferenceSize,
                40,
                40,
                this.settings.getWidth(),
                true,
                0.1f));
        
        // Register measuring instrument for altitude over ground and place in the HUD
        myHudControl.registerNewInstrument(new AltOverGroundControl(
                guiNode, assetManager,
                80,
                0,
                hudReferenceSize,
                40,
                8,
                this.settings.getWidth(),
                true,
                0.5f));
    }
    
    /**
     * Creates the light (and later shadow will be initialized here as well)
     */
    private void initLightAndShadow() {
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.5f,-0.5f,-0.5f).normalizeLocal());
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun);
    }
    
    
    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
        ((PlaneBasicControl) this.myPlane.planeNode.getControl(PlaneBasicControl.class)).movePlaneNode();
        
        // positioning of the camera should always be the last activity
        // in the simpleUpdate Method
        this.myCam.positionCam(tpf);
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        if(name.equals(KeyMapping.MOVE_PLANE_ROLL_RIGHT)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setRollRight(isPressed);
        } else if(name.equals(KeyMapping.MOVE_PLANE_ROLL_LEFT)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setRollLeft(isPressed);
        } else if(name.equals(KeyMapping.MOVE_PLANE_YAW_LEFT)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setYawLeft(isPressed);
        } else if(name.equals(KeyMapping.MOVE_PLANE_YAW_RIGHT)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setYawRight(isPressed);
        } else if(name.equals(KeyMapping.MOVE_PLANE_PITCH_DOWN)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setPitchDown(isPressed);
        } else if(name.equals(KeyMapping.MOVE_PLANE_PITCH_UP)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setPitchUp(isPressed);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_0)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.0f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_1)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.1f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_2)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.2f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_3)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.3f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_4)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.4f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_5)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.5f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_6)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.6f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_7)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.7f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_8)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.8f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_9)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(0.9f);
        } else if(name.equals(KeyMapping.ACCELERATE_PLANE_10)) {
            ((PlaneBasicControl)this.myPlane.planeNode.getControl(PlaneBasicControl.class)).setThrottle(1.0f);
        } else if (name.equals(KeyMapping.MOVE_CAM_TURN_RIGHT)) {
            this.myCam.setTurnRight(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_TURN_LEFT)) {
            this.myCam.setTurnLeft(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_PITCH_UP)) {
            this.myCam.setPitchUp(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_PITCH_DOWN)) {
            this.myCam.setPitchDown(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_MOVE_UP)) {
            this.myCam.setMoveUp(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_MOVE_DOWN)) {
            this.myCam.setMoveDown(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_MOVE_LEFT)) {
            this.myCam.setMoveLeft(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_MOVE_RIGHT)) {
            this.myCam.setMoveRight(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_MOVE_FORWARD)) {
            this.myCam.setMoveForward(isPressed);
        } else if (name.equals(KeyMapping.MOVE_CAM_MOVE_BACK)) {
            this.myCam.setMoveBack(isPressed);
        }
    }
}
